﻿using System;
using System.Timers;

using WebSocketSharp; // link: https://github.com/PingmanTools/websocket-sharp/
using Newtonsoft.Json; // link: https://www.newtonsoft.com/json
using Newtonsoft.Json.Linq;

namespace AsicMonitor
{
    public class Dat
    {
        public int uptime;
        public int temp;
        public double diff;
        public double[] hashrate;
        public int accept;
        public int reject;
        public int wrong;
        public int err;
    }

    /*
      {
        "miner_name":"V275-0118",
        "manage_ip":"0.0.0.0\/0",
        "url":"stratum+tcp:\/\/lyra2v2.mine.ahashpool.com:4533",
        "user":"174XAGK8JYzaBCdFHZ7fV6kDxCqbwGEiUT",
        "pass":"c=BTC,ID=V275",
        "algo":"lyra2rev2"
      */

    public class Misec_miner {
        public int freq_dec = 0;
    }


    public class Conf
    {
        public string miner_name;
        public Misec_miner misec_miner;
        public string manage_ip;
        public string url;
        public string user;
        public string pass;
        public string algo;
    }


    public class Miner
    {
        public class MinerDataEventArgs : EventArgs { }
        public delegate void MinerDataEventHandler(Object sender, MinerDataEventArgs e);

        public event MinerDataEventHandler DataModifyEvent;
        protected virtual void OnDataModifyEvent(MinerDataEventArgs e)
        {
            MinerDataEventHandler handler = DataModifyEvent;
            if(handler != null)
            {
                handler(this, e);
            }
        }

        public string Mac; // read from websocket first msg.
        public string IP;
        public string Space; // 
        public Conf Conf;
        public Dat Dat;

        private string state =  "init";

        private Timer timer;
        private WebSocket webSocket = null;
        private int reconnect_time = 0;
        private bool liveOnce = false;

        public static readonly int MAX_RECONNECT_TIME = 2;
        public static readonly int RECONNECT_INTERVAL = 2 * 1000;


        public Miner(string ip)
        {
            this.IP = ip;
        }

        public void StartWatch()
        {
            this.state = "init";

            Log.Info("start to websocket....");
            if (this.webSocket != null && this.webSocket.IsAlive) return;

            string url = "ws://" + this.IP;
            this.webSocket = new WebSocket(url);

            this.webSocket.OnMessage += this.OnMessage;
            this.webSocket.OnError += this.OnError;
            this.webSocket.OnClose += this.OnClose;
            this.webSocket.OnOpen += this.OnOpen;

            this.webSocket.ConnectAsync();
            this.state = "connecting";
            // this.webSocket.Connect();
        }

        public void StopWatch()
        {
            if (this.webSocket != null)
            {
                this.webSocket.OnMessage -= this.OnMessage;
                this.webSocket.OnError -= this.OnError;
                this.webSocket.OnClose -= this.OnClose;
                this.webSocket.OnOpen -= this.OnOpen;

                Log.Info("stop miner websocket...");

                if (this.webSocket.IsAlive)
                {
                    this.webSocket.CloseAsync();
                }
                this.webSocket = null;
            }
            this.state = "init";
        }

        public void Close()
        {
            this.StopWatch();
            this.state = "nonexist";
        }

        public string getState()
        {
            return state;

            if (this.webSocket == null)
                return "init";

            if (!this.webSocket.IsAlive)
            {
                if (this.reconnect_time < MAX_RECONNECT_TIME)
                    return "connecting";

                return this.Dat == null ? "nonexist" : "offline";
            }
            return this.Dat == null ? "connected" : "online";
        }


        private void OnMessage(object sender, MessageEventArgs e)
        {
            state = "online";
            liveOnce = true;

            Log.Info($"{this.IP} recieved: {e.Data}");

            // parse data
            string jsonString = e.Data;
            if(jsonString.Contains("I am FireStone Miner."))
            {//  I am FireStone Miner. MAC=88:5a:63:44:56:1e
                // pos = msg.indexOf('MAC=');
                // if (pos)
                //   that_vue.MAC = msg.substr(pos + 4, 17);
                if (jsonString.Contains("MAC"))
                {
                    this.Mac = jsonString.Substring(jsonString.IndexOf("MAC") + 4, 17);
                }

                SendGetConfMsg();
                OnDataModifyEvent(new MinerDataEventArgs());
                return;
            }

            /*
             * data: {
			sock: 0,
			conf: {
				miner_name : "",
				algo : "lyra2z",
				misec_miner :
				{
					freq_dec : 0,
				},
				url : "",
				user : "",
				pass : "",
			},
			stat: {
				locate_miner: false,
				err : 0,
				uptime : 0,
				temp : 0,
				diff : 0,
				hashrate : [0,0,0,0,0,0,0,0,0,0,0,0],
				accept : 0,
				reject : 0,
				wrong : 0,
			},
			input: {
				miner_name : "",
				algo : "lyra2z",
				misec_miner :
				{
					freq_dec : 0,
				},
				url : "",
				user : "",
				pass : "",
			},
			input_locate_miner: false,
			MAC: {},
			SW_VERSION: {},
			newpass: {
				oldpass: ''
			},
			dialog: false,
			fileList: [],
			PoolOptions:
			[{
					label: 'XZC',
					value: 'stratum+tcp://xzc.f2pool.com:5740'
				}, {
					label: 'GIN',
					value: 'stratum+tcp://gin.f2pool.com:20012'
				}, {
					label: 'Multi-coin',
					value: 'stratum+tcp://blockmasters.co:4553'
				}
			]
		},*/
        dynamic jsonObj = Newtonsoft.Json.Linq.JToken.Parse(jsonString) as dynamic;
        if (jsonObj != null && jsonObj.get != null)
        {
                //   Send(" {\"get\": \"stat\", \"dat\": { \"uptime\": 890868, \"temp\": 40, \"diff\": 256.000000, \"hashrate\": [403.00,501.08,310.19,329.28,386.55,381.77,386.55,375.81,388.14,395.49,387.54,389.38,389.06], \"accept\": 45206, \"reject\": 174, \"wrong\": 26, \"err\": 0}}");
                if (jsonObj.get == "stat" && jsonObj.dat != null)
                {
                    string datjson = JsonConvert.SerializeObject(jsonObj.dat);
                    Dat dat = JsonConvert.DeserializeObject<Dat>(datjson);
                    this.Dat = dat;
                }
                else if (jsonObj.get == "conf" && jsonObj.dat != null)
                {
                    /* {"get": "conf", "hw_version": "1.0", "sw_version": "20181126-V2-150", "mac": "88:5a:63:44:56:1e", "dat": 
                           {
                              "miner_name":"V275-0118",
                              "manage_ip":"0.0.0.0\/0",
                              "url":"stratum+tcp:\/\/lyra2v2.mine.ahashpool.com:4533",
                              "user":"174XAGK8JYzaBCdFHZ7fV6kDxCqbwGEiUT",
                              "pass":"c=BTC,ID=V275",
                              "algo":"lyra2rev2"
                                                }
                                            }
                     */
                    string datjson = JsonConvert.SerializeObject(jsonObj.dat);
                    Conf conf = JsonConvert.DeserializeObject<Conf>(datjson);
                    this.Conf = conf;
                }
            }

            OnDataModifyEvent(new MinerDataEventArgs());
        }

        private void OnError(object sender, ErrorEventArgs e)
        {
            if(state == "online")
            {
                SendOfflineNotifyToServer();
            }
            state = "offline";

            Log.Info(this.IP + " Error: " + e.Message);
        }

        private void OnClose(object sender, CloseEventArgs e)
        {
            if(state == "online")
            {
                SendOfflineNotifyToServer();
            }

            if(liveOnce)
            {
                state = "offline";
            }
            else
            {
                state = "nonexist";
            }
            Log.Info(this.IP + " Websocket Close: " + e.Code + "  reason " + e.Reason);

            OnDataModifyEvent(new MinerDataEventArgs());
            // this.RetryConnect();
        }

        private void OnOpen(object sender, EventArgs e)
        {
            state = "connected";
            Log.Info(this.IP + " Websocket Connected!");
            OnDataModifyEvent(new MinerDataEventArgs());
        }

        private void SendGetConfMsg()
        {
            if (this.webSocket != null && this.webSocket.IsAlive)
            {
                this.webSocket.Send("{ \"get\":\"conf\" }");
            }
        }

        public void sendSetConfMsg(Conf conf)
        {
            /*
            this.input.misec_miner.freq_dec = parseInt(this.input.misec_miner.freq_dec);
            freq = this.input.misec_miner.freq_dec;
            if (freq % 25 != 0 || freq < 0 || freq > 400)
            {
                this.$alert("降频设置错误！必须在0~400之间，且必须是25的倍数！");
            }
            else
            {
                this.sock.send(JSON.stringify({
                set: 'conf',
				dat: this.input
        
            }));
                this.dialog = false;
                this.$message({
                type: 'success',
			message: '已保存配置，软件将重启...'

            input: {
				miner_name : "",
				algo : "lyra2z",
				misec_miner :
				{
					freq_dec : 0,
				},
				url : "",
				user : "",
				pass : "",
			},
            */

            if (this.webSocket != null && this.webSocket.IsAlive)
            {
                Log.Debug("send miner conf...");
                int freq_dec = 0;
                if(conf.misec_miner != null && conf.misec_miner.freq_dec != 0)
                {
                    freq_dec = conf.misec_miner.freq_dec;
                    if(freq_dec < 0 || freq_dec > 400)
                    {
                        freq_dec = 0;
                    }
                    freq_dec = freq_dec - freq_dec % 25; // 必须是25的整数倍
                }

                JObject freq = new JObject
                {
                    { "freq_dec", freq_dec }
                };

                JObject dat = new JObject
                {
                    { "miner_name", conf.miner_name },
                    { "algo", conf.algo },
                    { "url", conf.url },
                    { "user", conf.user },
                    { "misec_miner", freq },
                    { "pass", conf.pass }
                };
                JObject o = new JObject
                {
                    { "set", "conf" },
                    { "dat",  dat }
                };
                // todo miner name
                Log.Info(o.ToString());
                Console.WriteLine(o.ToString());
                this.webSocket.SendAsync(o.ToString(), null);
            }
        }
        
        public void trySendGetConfIfConfNull()
        {
            if(this.Conf == null)
            {
                SendGetConfMsg();
            }
        }


        public bool checkConfMatch(Conf newConf)
        {
            if (this.Conf == null || newConf == null) return false;
            if (this.Conf.url != newConf.url
                || this.Conf.user != newConf.user) return false; 

            return true;
        }

        private void RetryConnect()
        {
            Log.Debug("start to reconnect_time + " + reconnect_time);
            if (this.reconnect_time < MAX_RECONNECT_TIME) return;

            this.reconnect_time++;
            
            if(timer != null)
            {
                timer.Stop();
                timer.Close();
            }
            timer = new Timer(RECONNECT_INTERVAL);
            timer.Elapsed += new ElapsedEventHandler(
                (sender, e) => {
                    this.StopWatch();
                    this.StartWatch(); 
            });    //到达时间的时候执行事件；
            timer.AutoReset = false;    //设置是执行一次（false）还是一直执行(true)；
            timer.Enabled = true;//是否执行System.Timers.Timer.Elapsed事件；
        }

       
        public void HealthCheck()
        {
            if (this.webSocket != null && this.webSocket.IsAlive)
            {
                Log.Debug("send heartbeat...");
                this.webSocket.SendAsync("HeartBeat", null);
            }
        }

        public void reconnectIfOffline()
        {
            if(this.state == "offline" || this.state == "close")
            {
                this.StopWatch();
                this.StartWatch();
            }
        }

        override public string ToString()
        {
            return $"IP: {this.IP}, state: {this.getState()}, Conf: {this.Conf}, Dat: {this.Dat}";
        }

        private void SendOfflineNotifyToServer()
        {
            // start to send offline notify to server.
            var url = "http://asic.aionpool.cn/monitor/api/notify";
            var param = new
            {
                content = $"矿场: {Space}  矿机:{Conf.miner_name}({IP}) 掉线"
            };
            HttpHelper.PostJsonAsync(url, param);
        }
    }

}
